package com.zy.explore.client.infrastructure.result;

import com.zy.explore.client.interfaces.vo.ResultVO;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * ResultVO 自动【包装】
 * RestController 可以直接返回对象，不用手动包装为 ResultVO
 *
 * @author 🇨🇳中国印🇨🇳
 * @since 2020/8/27
 */
@RestControllerAdvice(basePackages = "com.zy.explore.client")
@ConditionalOnProperty(value = ResultVOConfigurer.ENABLED, matchIfMissing = true)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class ResultVOAdvice implements ResponseBodyAdvice<Object> {

    private static final List<String> DEFAULT_EXCLUDE_PATHS = Arrays.asList(
        "/**/v2/api-docs",
        "/**/swagger-resources",
        "/**/actuator",
        "/**/actuator/info",
        "/**/actuator/health",
        "/**/actuator/health/{*path}"
    );

    private final HttpServletRequest request;

    private final List<String> excludePaths = new ArrayList<>();

    ResultVOAdvice(HttpServletRequest request, ResultVOConfigurer configurer) {
        this.request = request;
        this.excludePaths.addAll(DEFAULT_EXCLUDE_PATHS);
        this.excludePaths.addAll(configurer.getExcludePath());
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> clazz) {

        boolean shouldDisabledByPath = excludeByPath(request.getRequestURI());
        if (shouldDisabledByPath) {
            return false;
        }

        boolean shouldDisabledByAnnotation = methodParameter.hasMethodAnnotation(DisableResultVOAdvice.class);
        if (shouldDisabledByAnnotation) {
            return false;
        }

        Method method = Objects.requireNonNull(methodParameter.getMethod());
        boolean isReturnResultVO = method.getReturnType().equals(ResultVO.class);
        return !isReturnResultVO;
    }

    private boolean excludeByPath(String uri) {
        return excludePaths.stream().anyMatch(uri::contentEquals);
    }

    @Override
    public Object beforeBodyWrite(@Nullable Object returnValue, MethodParameter parameter, MediaType type, Class<? extends HttpMessageConverter<?>> clazz, ServerHttpRequest request, ServerHttpResponse response) {

        if (returnValue instanceof ResponseEntity) {
            return wrapByResponseEntity(((ResponseEntity<?>) returnValue));
        }

        return wrapByResultVO(returnValue);
    }

    private ResponseEntity<?> wrapByResponseEntity(ResponseEntity<?> entity) {
        return ResponseEntity
            .status(entity.getStatusCode())
            .headers(entity.getHeaders())
            .body(wrapByResultVO(entity.getBody()));
    }

    private ResultVO<?> wrapByResultVO(@Nullable Object value) {
        if (value instanceof ResultVO) {
            return (ResultVO<?>) value;
        }

        return value == null ? ResultVO.getSuccess() : ResultVO.getSuccess(value);
    }
}
